﻿using Grow.Domain.Repository;
using Grow.Module.Service;

namespace Grow.Domain.EventBus.Cap.SubscriberDomainEvents
{
    public class SubscriberDomainEventService : ISubscriberDomainEventService
    {
        private readonly IServiceContext _serviceContext;
        private readonly DomainEventBusOptions _domainEventBusOptions;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly ISubscriberDomainEventDomainRepository _subscriberDomainEventDomainRepository;
        public SubscriberDomainEventService(IServiceContext serviceContext)
        {
            _serviceContext = serviceContext;
            _domainEventBusOptions = _serviceContext.GetOptions<DomainEventBusOptions>();
            _unitOfWorkManager = _serviceContext.GetRequiredService<IUnitOfWorkManager>();
            _subscriberDomainEventDomainRepository = _serviceContext.GetRequiredService<ISubscriberDomainEventDomainRepository>();
        }

        public async Task<bool> LockSubscriberEventAsync(Guid Id, CancellationToken cancellationToken)
        {
            DateTimeOffset now = DateTimeOffset.Now;
            DateTimeOffset lockEndTime = DateTimeOffset.Now.AddSeconds(_domainEventBusOptions.LockTime);

            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            SubscriberDomainEvent? subscriberEventData = await _subscriberDomainEventDomainRepository.FirstOrDefaultAsync(a =>
                 a.Id == Id
                && (a.LockEndTime < now || a.LockEndTime == null), cancellationToken);

            if (subscriberEventData is null)
            {
                return false;
            }
            subscriberEventData.LockEndTime = lockEndTime;

            await _subscriberDomainEventDomainRepository.UpdateAsync(subscriberEventData, cancellationToken);

            await unitOfWork.CommitAsync(cancellationToken);
            return true;
        }



        public async Task UpdateSubscriberEventAsync(Guid Id, SubscriberDomainEventStatus subscriberDomainEventStatus, CancellationToken cancellationToken)
        {
            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            SubscriberDomainEvent? subscriberEvent = await _subscriberDomainEventDomainRepository.FirstOrDefaultAsync(a =>
                a.Id == Id, cancellationToken);

            if (subscriberEvent is null)
            {
                return;
            }
            subscriberEvent.EventStatus = subscriberDomainEventStatus;
            subscriberEvent.RetryCount++;
            if (subscriberDomainEventStatus == SubscriberDomainEventStatus.Success)
            {
                subscriberEvent.ExpireTime = DateTimeOffset.Now.AddHours(_domainEventBusOptions.SucceedExpireHour);
            }

            await _subscriberDomainEventDomainRepository.UpdateAsync(subscriberEvent, cancellationToken);

            await unitOfWork.CommitAsync(cancellationToken);
        }


        public async Task SaveSubscriberEventAsync(SubscriberDomainEventDto subscriberDomainEventDto, CancellationToken cancellationToken)
        {
            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            SubscriberDomainEvent subscriberDomainEvent = new(
               subscriberDomainEventDto.Id,
               _domainEventBusOptions.EnvironmentName,
               subscriberDomainEventDto.EventName,
               subscriberDomainEventDto.EventType,
               subscriberDomainEventDto.EventHandlerType,
               subscriberDomainEventDto.EventBody,
               SubscriberDomainEventStatus.Wait,
               0,
               subscriberDomainEventDto.SendAt,
               null,
               null);

            await _subscriberDomainEventDomainRepository.AddAsync(subscriberDomainEvent, cancellationToken);
            await unitOfWork.CommitAsync(cancellationToken);
        }

        public async Task ExpirerSubscriberDomainEventAsync(CancellationToken cancellationToken)
        {
            DateTimeOffset now = DateTimeOffset.Now;
            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            List<SubscriberDomainEvent> subscriberDomainEvents = await _subscriberDomainEventDomainRepository.ToListAsync(a =>
                a.ExpireTime < now && a.ExpireTime != null
                && a.EventStatus == SubscriberDomainEventStatus.Success
               , cancellationToken);

            foreach (SubscriberDomainEvent subscriberDomainEvent in subscriberDomainEvents)
            {
                await _subscriberDomainEventDomainRepository.RemoveAsync(subscriberDomainEvent, cancellationToken);
            }
            await unitOfWork.CommitAsync(cancellationToken);
        }

        public async Task<List<SubscriberDomainEventDto>> GetSubscriberDomainEventsOfNeedRetryAsync(CancellationToken cancellationToken)
        {
            DateTimeOffset now = DateTimeOffset.Now;
            DateTimeOffset sendAt = now.AddSeconds(-_domainEventBusOptions.StartRetryAfter);


            List<SubscriberDomainEvent> subscriberDomainEvents = await _subscriberDomainEventDomainRepository.ToListAsync(a =>
                a.SendAt < sendAt
                && a.EnvironmentName == _domainEventBusOptions.EnvironmentName
                && a.RetryCount < _domainEventBusOptions.RetryLimitCount
                && (a.LockEndTime < now || a.LockEndTime == null)
                && (a.EventStatus == SubscriberDomainEventStatus.Wait || a.EventStatus == SubscriberDomainEventStatus.Fail)
                , _domainEventBusOptions.RetryFailedMax, cancellationToken);

            return subscriberDomainEvents.Select(a => new SubscriberDomainEventDto(
                    a.Id,
                    a.EventName,
                    a.EventType,
                    a.EventHandlerType,
                    a.EventBody,
                    a.EventStatus,
                    a.RetryCount,
                    a.SendAt,
                    a.LockEndTime,
                    a.ExpireTime)).ToList();
        }
    }
}
